package com.snpic.appaw.gateway.filter.security;

import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.snpic.appaw.framework.common.core.KeyValue;
import com.snpic.appaw.framework.common.pojo.CommonResult;
import com.snpic.appaw.framework.common.util.date.LocalDateTimeUtils;
import com.snpic.appaw.framework.common.util.json.JsonUtils;
import com.snpic.appaw.gateway.util.SecurityFrameworkUtils;
import com.snpic.appaw.gateway.util.WebFrameworkUtils;
import com.snpic.appaw.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
import com.snpic.appaw.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.util.Objects;
import java.util.function.Function;

import static com.snpic.appaw.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.snpic.appaw.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;

/**
 * Token 过滤器，验证 token 的有效性
 * 1. 验证通过时，将 userId、userType、tenantId 通过 Header 转发给服务
 * 2. 验证不通过，还是会转发给服务。因为，接口是否需要登录的校验，还是交给服务自身处理
 *
 * @author 芋道源码
 */
@Component
@Slf4j
public class TokenAuthenticationFilter implements GlobalFilter, Ordered {

	/**
	 * CommonResult<OAuth2AccessTokenCheckRespDTO> 对应的 TypeReference 结果，用于解析 checkToken 的结果
	 */
	private static final TypeReference<CommonResult<OAuth2AccessTokenCheckRespDTO>> CHECK_RESULT_TYPE_REFERENCE
		= new TypeReference<CommonResult<OAuth2AccessTokenCheckRespDTO>>() {
	};

	/**
	 * 空的 LoginUser 的结果
	 * <p>
	 * 用于解决如下问题：
	 * 1. {@link #getLoginUser(ServerWebExchange, String)} 返回 Mono.empty() 时，会导致后续的 flatMap 无法进行处理的问题。
	 * 2. {@link #buildUser(String)} 时，如果 Token 已经过期，返回 LOGIN_USER_EMPTY 对象，避免缓存无法刷新
	 */
	private static final LoginUser LOGIN_USER_EMPTY = new LoginUser();

	private final WebClient webClient;

	/**
	 * 登录用户的本地缓存
	 * <p>
	 * key1：多租户的编号
	 * key2：访问令牌
	 */
	private final LoadingCache<KeyValue<Long, String>, LoginUser> loginUserCache = buildAsyncReloadingCache(Duration.ofMinutes(1),
		new CacheLoader<KeyValue<Long, String>, LoginUser>() {

			@Override
			public LoginUser load(KeyValue<Long, String> token) {
				String body = checkAccessToken(token.getKey(), token.getValue()).block();
				return buildUser(body);
			}

		});

	public TokenAuthenticationFilter(ReactorLoadBalancerExchangeFilterFunction lbFunction) {
		// Q：为什么不使用 OAuth2TokenApi 进行调用？
		// A1：Spring Cloud OpenFeign 官方未内置 Reactive 的支持 https://docs.spring.io/spring-cloud-openfeign/docs/current/reference/html/#reactive-support
		// A2：校验 Token 的 API 需要使用到 header[tenant-id] 传递租户编号，暂时不想编写 RequestInterceptor 实现
		// 因此，这里采用 WebClient，通过 lbFunction 实现负载均衡
		this.webClient = WebClient.builder().filter(lbFunction).build();
	}


	@Override
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
			// 移除 login-user 的请求头，避免伪造模拟
			exchange = SecurityFrameworkUtils.removeLoginUser(exchange);

			// 情况一，如果没有 Token 令牌，则直接继续 filter
			String token = SecurityFrameworkUtils.obtainAuthorization(exchange);
			if (StrUtil.isEmpty(token)) {
					return chain.filter(exchange);
			}

			// 情况二，如果有 Token 令牌，则解析对应 userId、userType、tenantId 等字段，并通过 通过 Header 转发给服务
			// 重要说明：defaultIfEmpty 作用，保证 Mono.empty() 情况，可以继续执行 `flatMap 的 chain.filter(exchange)` 逻辑，避免返回给前端空的 Response！！
			ServerWebExchange finalExchange = exchange;
			return getLoginUser(exchange, token).defaultIfEmpty(LOGIN_USER_EMPTY).flatMap(user -> {
					// 1. 无用户，直接 filter 继续请求
					if (user == LOGIN_USER_EMPTY || // 下面 expiresTime 的判断，为了解决 token 实际已经过期的情况
									user.getExpiresTime() == null || LocalDateTimeUtils.beforeNow(user.getExpiresTime())) {
							return chain.filter(finalExchange);
					}

					// 2.1 有用户，则设置登录用户
					SecurityFrameworkUtils.setLoginUser(finalExchange, user);
					// 2.2 将 user 并设置到 login-user 的请求头，使用 json 存储值
					ServerWebExchange newExchange = finalExchange.mutate()
									.request(builder -> SecurityFrameworkUtils.setLoginUserHeader(builder, user)).build();
					return chain.filter(newExchange);
			});
	}
	/*
	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
		// 移除旧的登录用户头
		exchange = SecurityFrameworkUtils.removeLoginUser(exchange);

		// 获取token
		String token = SecurityFrameworkUtils.obtainAuthorization(exchange);
		if (StrUtil.isEmpty(token)) {
			return chain.filter(exchange);
		}

		//调用私有方法验证token
		boolean verifyToken = verifyToken(token);
		String secret = "${JWT_SECRET:tit@2023#SecretKey!}";
		try {
        // 完整验证（包含签名校验和过期时间验证）
        DecodedJWT jwt = JWT.require(Algorithm.HMAC256(secret))
                .withIssuer("tit-system")
                .build()
                .verify(token); // 这里会同时验证签名和过期时间

     // 构建用户信息
        LoginUser loginUser = new LoginUser()
                .setId(jwt.getClaim("userId").asLong())
								.setChannelCode(jwt.getClaim("channelCode").asString());
			// 设置用户信息到请求头

        ServerWebExchange newExchange = exchange.mutate()
                .request(builder -> {
                    SecurityFrameworkUtils.setLoginUserHeader(builder, loginUser);
                    builder.header("X-User-Id", String.valueOf(loginUser.getId()));
                    builder.header("X-Channel-Code", String.valueOf(loginUser.getChannelCode()));
                })
                .build();

        return chain.filter(newExchange);
		} catch (JwtException e) {
			// 处理JWT验证失败
			exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
			return exchange.getResponse().setComplete();
		}
	}*/

	private boolean verifyToken(String token) {
		if (StrUtil.isBlank(token)) {
			return false;
		}
		String secret = "${JWT_SECRET:tit@2023#SecretKey!}";
		try {
			Algorithm algorithm = Algorithm.HMAC256(secret);
			JWT.require(algorithm)
				.withIssuer("tit-system")
				.build()
				.verify(token); // 自动验证有效期（exp 声明）
			System.out.println("Token 验证成功（未过期且签名有效）");
			return true;
		} catch (TokenExpiredException e) {
			// 捕获 Token 过期异常，明确输出过期信息
			System.out.println("Token 已过期：" + e.getMessage());
			return false;
		} catch (JWTVerificationException e) {
			// 其他验证失败（如签名错误、签发者不匹配等）
			System.out.println("Token 验证失败（非过期原因）：" + e.getMessage());
			return false;
		}
	}


	private Mono<LoginUser> getLoginUser(ServerWebExchange exchange, String token) {
		// 从缓存中，获取 LoginUser
		Long tenantId = WebFrameworkUtils.getTenantId(exchange);
		KeyValue<Long, String> cacheKey = new KeyValue<Long, String>().setKey(tenantId).setValue(token);
		LoginUser localUser = loginUserCache.getIfPresent(cacheKey);
		if (localUser != null) {
			return Mono.just(localUser);
		}

		// 缓存不存在，则请求远程服务
		return checkAccessToken(tenantId, token).flatMap((Function<String, Mono<LoginUser>>) body -> {
			LoginUser remoteUser = buildUser(body);
			if (remoteUser != null) {
				// 非空，则进行缓存
				loginUserCache.put(cacheKey, remoteUser);
				return Mono.just(remoteUser);
			}
			return Mono.empty();
		});
	}

	private Mono<String> checkAccessToken(Long tenantId, String token) {
		return webClient.get()
			.uri(OAuth2TokenCommonApi.URL_CHECK, uriBuilder -> uriBuilder.queryParam("accessToken", token).build())
			.headers(httpHeaders -> WebFrameworkUtils.setTenantIdHeader(tenantId, httpHeaders)) // 设置租户的 Header
			.retrieve().bodyToMono(String.class);
	}

	private LoginUser buildUser(String body) {
		// 处理结果，结果不正确
		CommonResult<OAuth2AccessTokenCheckRespDTO> result = JsonUtils.parseObject(body, CHECK_RESULT_TYPE_REFERENCE);
		if (result == null) {
			return null;
		}
		if (result.isError()) {
			// 特殊情况：令牌已经过期（code = 401），需要返回 LOGIN_USER_EMPTY，避免 Token 一直因为缓存，被误判为有效
			if (Objects.equals(result.getCode(), HttpStatus.UNAUTHORIZED.value())) {
				return LOGIN_USER_EMPTY;
			}
			return null;
		}

		// 创建登录用户
		OAuth2AccessTokenCheckRespDTO tokenInfo = result.getData();
		return new LoginUser().setId(tokenInfo.getUserId()).setUserType(tokenInfo.getUserType())
			.setInfo(tokenInfo.getUserInfo()) // 额外的用户信息
			.setTenantId(tokenInfo.getTenantId()).setScopes(tokenInfo.getScopes())
			.setExpiresTime(tokenInfo.getExpiresTime());
	}

	@Override
	public int getOrder() {
		return -100; // 和 Spring Security Filter 的顺序对齐
	}

}
